package com.example.springboot_demo.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/*
 * 这是一个切面类
 */
@Component   //将通知类加入到IOC容器
@Aspect         //告诉IOS容器当前bean是一个通知bean
@Slf4j
@SuppressWarnings("all")
public class AspectjAdvice {
    //1.配置切点         默认情况下方法名()  作为切点的id
    //@Pointcut("execution(* com.example.springboot_demo.controller.*.*(..))")
    @Pointcut("@annotation(com.example.springboot_demo.aop.annotation.Log)")
    public void _pc() {
    }

    //2.前置通知
    @Before(value = "_pc()")
    public void before() {
       log.info("前置通知：开启事务");
    }

    //3.后置通知：在方法函数体结束之后，不管方法是否正常结束，都会执行
    @After(value = "_pc()")
    public void after() {
        log.info("后置通知");
    }

    //4.返回后通知：在return之后执行
    @AfterReturning(pointcut = "_pc()", returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result) {
        log.info("返回后通知：结果{} {}",joinPoint.getSignature().getName(), result);
    }

    //5.异常通知
    @AfterThrowing(pointcut = "_pc()",throwing = "throwable")
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable) {
        log.error("异常通知： 异常信息：{} {}",joinPoint.getSignature().getName(), throwable.getMessage());
    }

    //6.环绕通知：简单认为  <=> 前置+异常+后置      可以用于日志处理、监控等
    //ProceedingJoinPoint对象封装了被代理类对象、方法、参数等等
    //只在环绕通知里面使用
    @Around("_pc()")
    public Object around(ProceedingJoinPoint point) {


        //例如：日志
        JoinPoint joinPoint = (JoinPoint) point;
        //得到调用方法的签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //通过被代理对象得到类名
        String className = joinPoint.getTarget().getClass().getName();
        System.out.println(className);
        //得到方法对象
        Method method = methodSignature.getMethod();
        //获取方法名(全路径:包名+类名+方法名)
        String name = method.getName();
        //日期信息：时间 - 类名 - 方法名(参数) - 结果
        System.out.println(name);
        log.info("环绕前：开启事务");
        //执行切点的代码
        try {
            Object proceed = point.proceed();
            //提交
            log.info("提交");
            return proceed;
        } catch (Throwable e) {
            //回滚
            log.info("回滚");
            e.printStackTrace();
            //记录日志信息
        } finally {
            //后置通知
            log.info("环绕后");
        }
        return null;
    }
}
